Imports system.Xml.Serialization
Imports system.io
Public Class EventDB

#Region "Define"
    Private Const XmlFilename As String = "EventDB.xml"
    Private ArmoryDataPath As String = "..\..\ArmoryData"

    Public Structure EventType
        <XmlAttribute()> Dim eventName As String
        <XmlAttribute()> Dim eventIdx As EventIndex
        <XmlAttribute()> Dim argc As Integer
        <XmlAttribute()> Dim argv() As ArgStyles
    End Structure

    Public Structure ArgEntry
        <XmlAttribute()> Dim style As ArgStyles
        <XmlAttribute()> Dim argc As Integer
    End Structure

    Public Enum ArgStyles
        None0
        Dummy1

        'Actor3      'id, name, flag
        'Target3

        Spell3      'spellId, spellName, spellSchool
        Enchant3    'spellName, itemId, itemName

        EnvType1

        Aura1       'auraType
        AuraDoze2   'auraType, amount

        Miss1       'missType
        Miss2       'missType, Absorption ... WOTLK 3.2

        ExtraSpell3  'spellId, spellName, spellSchool

        Damage8     'amount, school, resisted, blocked, absorbed, critical, glancing, crushing
        Damage9     'amount, absorption, school, resisted, blocked, absorbed, critical, glancing, crushing

        Heal2       'amount + crit ... TBC
        Heal3       'amount + overheal + crit ... WOTLK 3.0
        Heal4       'amount + overheal + absorption + crit ... WOTLK 3.2

        Energize2   'amount, powerType
        Drain3      'amount, powerType, extraAmount
    End Enum

    Private EventArgcEntries() As EventType

    Public Structure EventDataEntry
        <XmlAttribute()> Dim eventDate As DateTime
        <XmlAttribute()> Dim eventIdx As EventIndex

        <XmlAttribute()> Dim actorId As Long
        <XmlAttribute()> Dim actorName As String
        <XmlAttribute()> Dim actorFlag As UInteger
        <XmlAttribute()> Dim targetId As Long
        <XmlAttribute()> Dim targetName As String
        <XmlAttribute()> Dim targetFlag As UInteger

        <XmlAttribute()> Dim spellId As Integer
        <XmlAttribute()> Dim spellName As String
        <XmlAttribute()> Dim spellSchool As Integer
        <XmlAttribute()> Dim exSpellId As Integer
        <XmlAttribute()> Dim exSpellName As String
        <XmlAttribute()> Dim exSpellschool As Integer

        <XmlAttribute()> Dim itemId As Integer
        <XmlAttribute()> Dim itemName As String

        <XmlAttribute()> Dim envType As String
        <XmlAttribute()> Dim auraType As String

        <XmlAttribute()> Dim amount As Integer
        <XmlAttribute()> Dim extraAmount As Integer

        <XmlAttribute()> Dim damageDone As Integer
        <XmlAttribute()> Dim damageAbsorb As Integer
        <XmlAttribute()> Dim heal As Integer
        <XmlAttribute()> Dim overHeal As Integer
        <XmlAttribute()> Dim healAbsorb As Integer
        <XmlAttribute()> Dim manaGain As Integer
        <XmlAttribute()> Dim energyGain As Integer

        <XmlAttribute()> Dim missType As String
        <XmlAttribute()> Dim powerType As String

        <XmlAttribute()> Dim resisted As Integer
        <XmlAttribute()> Dim blocked As Integer
        <XmlAttribute()> Dim absorbed As Integer
        <XmlAttribute()> Dim critical As Integer
        <XmlAttribute()> Dim glancing As Integer
        <XmlAttribute()> Dim crushing As Integer
    End Structure

    Public Enum EventIndex As Short
        UNKNOWN
        SWING_DAMAGE
        SWING_MISSED
        RANGE_DAMAGE
        RANGE_MISSED
        SPELL_DAMAGE
        SPELL_MISSED
        SPELL_HEAL
        SPELL_ENERGIZE
        SPELL_PERIODIC_MISSED
        SPELL_PERIODIC_DAMAGE
        SPELL_PERIODIC_HEAL
        SPELL_PERIODIC_DRAIN
        SPELL_PERIODIC_LEECH
        SPELL_PERIODIC_ENERGIZE
        SPELL_DRAIN
        SPELL_LEECH
        SPELL_INTERRUPT
        SPELL_EXTRA_ATTACKS
        SPELL_INSTAKILL
        SPELL_DURABILITY_DAMAGE
        SPELL_DURABILITY_DAMAGE_ALL
        SPELL_DISPEL_FAILED
        SPELL_AURA_DISPELLED
        SPELL_AURA_STOLEN
        SPELL_AURA_APPLIED
        SPELL_AURA_REMOVED
        SPELL_AURA_APPLIED_DOSE
        SPELL_AURA_REMOVED_DOSE
        SPELL_CAST_START
        SPELL_CAST_SUCCESS
        SPELL_CAST_FAILED
        DAMAGE_SHIELD
        DAMAGE_SHIELD_MISSED
        ENCHANT_APPLIED
        ENCHANT_REMOVED
        ENVIRONMENTAL_DAMAGE
        DAMAGE_SPLIT
        UNIT_DIED
        SPELL_SUMMON
        SPELL_CREATE
        PARTY_KILL
        UNIT_DESTROYED
        SPELL_AURA_REFRESH
        SPELL_AURA_BROKEN_SPELL
        SPELL_DISPEL
        SPELL_STOLEN
        SPELL_AURA_BROKEN
        SPELL_RESURRECT
        SPELL_BUILDING_DAMAGE
    End Enum

    Public EventNames() As String = { _
        "UNKNOWN", _
        "SWING_DAMAGE", _
        "SWING_MISSED", _
        "RANGE_DAMAGE", _
        "RANGE_MISSED", _
        "SPELL_DAMAGE", _
        "SPELL_MISSED", _
        "SPELL_HEAL", _
        "SPELL_ENERGIZE", _
        "SPELL_PERIODIC_MISSED", _
        "SPELL_PERIODIC_DAMAGE", _
        "SPELL_PERIODIC_HEAL", _
        "SPELL_PERIODIC_DRAIN", _
        "SPELL_PERIODIC_LEECH", _
        "SPELL_PERIODIC_ENERGIZE", _
        "SPELL_DRAIN", _
        "SPELL_LEECH", _
        "SPELL_INTERRUPT", _
        "SPELL_EXTRA_ATTACKS", _
        "SPELL_INSTAKILL", _
        "SPELL_DURABILITY_DAMAGE", _
        "SPELL_DURABILITY_DAMAGE_ALL", _
        "SPELL_DISPEL_FAILED", _
        "SPELL_AURA_DISPELLED", _
        "SPELL_AURA_STOLEN", _
        "SPELL_AURA_APPLIED", _
        "SPELL_AURA_REMOVED", _
        "SPELL_AURA_APPLIED_DOSE", _
        "SPELL_AURA_REMOVED_DOSE", _
        "SPELL_CAST_START", _
        "SPELL_CAST_SUCCESS", _
        "SPELL_CAST_FAILED", _
        "DAMAGE_SHIELD", _
        "DAMAGE_SHIELD_MISSED", _
        "ENCHANT_APPLIED", _
        "ENCHANT_REMOVED", _
        "ENVIRONMENTAL_DAMAGE", _
        "DAMAGE_SPLIT", _
        "UNIT_DIED", _
        "SPELL_SUMMON", _
        "SPELL_CREATE", _
        "PARTY_KILL", _
        "UNIT_DESTROYED", _
        "SPELL_AURA_REFRESH", _
        "SPELL_AURA_BROKEN_SPELL", _
        "SPELL_DISPEL", _
        "SPELL_STOLEN", _
        "SPELL_AURA_BROKEN", _
        "SPELL_RESURRECT", _
        "SPELL_BUILDING_DAMAGE" _
    }

#End Region

#Region "Init & Prop"

    Public Sub New()

        ReDim EventArgcEntries(0)
        EventArgcEntries(0) = New EventType
        EventArgcEntries(0).eventName = "UNKNOWN"
        EventArgcEntries(0).argc = 0

        Dim XU As New XmlUtils.AccessXml
        Try
            XU.ImportFromXml(EventArgcEntries, getXmlFilename(XmlFilename))
        Catch ex As Exception
            Dim EH As New ErrorHandler
            EH.ErrorMsgBox(ex, "Events.New ImportFromXml")
        End Try
    End Sub

    Protected Overrides Sub Finalize()
        Dim XU As New XmlUtils.AccessXml
        Try
            XU.ExportByXml(EventArgcEntries, getXmlFilename(XmlFilename))
        Catch ex As Exception
            Dim EH As New ErrorHandler
            EH.ErrorMsgBox(ex, "Events.Finalize ExportByXml")
        End Try

        MyBase.Finalize()
    End Sub

    Private Function getXmlFilename(ByVal filename As String) As String
        Dim path As String = My.Computer.FileSystem.SpecialDirectories.AllUsersApplicationData
        Return My.Computer.FileSystem.CombinePath(My.Computer.FileSystem.CombinePath(path, ArmoryDataPath), filename)
    End Function

    Dim m_Year As Integer = Now.Year
    Public Property Year() As Integer
        Get
            Return m_Year
        End Get
        Set(ByVal value As Integer)
            If value > 2000 And value <= Now.Year Then
                m_Year = value
            End If
        End Set
    End Property
#End Region

#Region "Parse"

    Public Function Parse(ByRef EventString As String(), ByRef EventData As EventDataEntry) As EventDataEntry
        CountEventArgc(EventString)
        ReadEvent(EventString, EventData)
        Return EventData
    End Function

    Private Sub CountEventArgc(ByRef EventString As String())
        Dim i As Integer
        Dim EH As New ErrorHandler

        If EventString Is Nothing Then
            Return
        ElseIf EventString.Length = 0 Then
            Return
        ElseIf EventString.Length < 2 Then
            EH.ErrorLog("EventDB", "CountEventArgc", "Combatlog line is too short", "DateTime=" & EventString(0) & " Count=" & EventString.Length)
        End If


        Dim Exists As Boolean = False
        For i = 0 To EventArgcEntries.Length - 1
            If EventString(1) = EventArgcEntries(i).eventName And EventString.Length = EventArgcEntries(i).argc Then
                Exists = True
                Exit For
            End If
        Next
        If Not Exists Then
            ReDim Preserve EventArgcEntries(EventArgcEntries.Length)
            EventArgcEntries(EventArgcEntries.Length - 1) = New EventType
            EventArgcEntries(EventArgcEntries.Length - 1).eventName = EventString(1)
            EventArgcEntries(EventArgcEntries.Length - 1).eventIdx = GetEventIdx(EventString(1))
            EventArgcEntries(EventArgcEntries.Length - 1).argc = EventString.Length

            EH.ErrorLog("Events", "CountEventArgc", "New Event or New count of args for the Event", "Event=" & EventString(1) & " Count=" & EventString.Length)
        End If
    End Sub

    Public Sub InitEventData(ByRef EventData As EventDataEntry)
        EventData.eventDate = "#1/1/2000#"
        EventData.eventIdx = 0

        EventData.actorId = 0
        EventData.actorName = ""
        EventData.actorFlag = 0
        EventData.targetId = 0
        EventData.targetName = ""
        EventData.targetFlag = 0

        EventData.spellId = 0
        EventData.spellName = ""
        EventData.spellSchool = 0
        EventData.exSpellId = 0
        EventData.exSpellName = ""
        EventData.exSpellschool = 0

        EventData.itemId = 0
        EventData.itemName = ""

        EventData.envType = ""
        EventData.auraType = ""

        EventData.amount = 0
        EventData.extraAmount = 0

        EventData.damageDone = 0
        EventData.damageAbsorb = 0
        EventData.heal = 0
        EventData.overHeal = 0
        EventData.healAbsorb = 0
        EventData.manaGain = 0
        EventData.energyGain = 0

        EventData.missType = ""
        EventData.powerType = ""

        EventData.resisted = 0
        EventData.blocked = 0
        EventData.absorbed = 0
        EventData.critical = 0
        EventData.glancing = 0
        EventData.crushing = 0
    End Sub

    Private Sub ReadEvent(ByRef EventString As String(), ByRef EventData As EventDataEntry)
        InitEventData(EventData)

        If EventString.Length < 2 Then
            Return
        End If

        'date time
        Dim pt As Integer = 0

        Dim cultureOld As String
        cultureOld = System.Globalization.CultureInfo.CurrentCulture.Name

        My.Application.ChangeCulture("ja-JP")
        Dim dt As String = Year & "/" & EventString(pt)
        EventData.eventDate = CDate(dt)
        My.Application.ChangeCulture(cultureOld)

        'event
        Dim ev As Integer
        Dim i As Integer
        For ev = 0 To EventArgcEntries.Length - 1
            If EventString(1) = EventArgcEntries(ev).eventName And EventString.Length = EventArgcEntries(ev).argc Then
                pt = 1
                EventData.eventIdx = EventArgcEntries(ev).eventIdx : pt += 1

                pt = 2
                EventData.actorId = GetUnitId(EventString(pt)) : pt += 1
                EventData.actorName = EventString(pt) : pt += 1
                EventData.actorFlag = ClangHexToInt(EventString(pt)) : pt += 1

                pt = 5
                EventData.targetId = GetUnitId(EventString(pt)) : pt += 1
                EventData.targetName = EventString(pt) : pt += 1
                EventData.targetFlag = ClangHexToInt(EventString(pt)) : pt += 1

                pt = 8
                If EventArgcEntries(ev).argv IsNot Nothing Then

                    For i = 0 To EventArgcEntries(ev).argv.Length - 1
                        Select Case EventArgcEntries(ev).argv(i)
                            Case ArgStyles.None0

                            Case ArgStyles.Dummy1
                                pt += 1

                            Case ArgStyles.Spell3      'spellId, spellName, spellSchool
                                EventData.spellId = EventString(pt) : pt += 1
                                EventData.spellName = EventString(pt) : pt += 1
                                EventData.spellSchool = ClangHexToInt(EventString(pt)) : pt += 1

                            Case ArgStyles.ExtraSpell3  'spellId, spellName, spellSchool
                                EventData.exSpellId = EventString(pt) : pt += 1
                                EventData.exSpellName = EventString(pt) : pt += 1
                                EventData.exSpellschool = ClangHexToInt(EventString(pt)) : pt += 1

                            Case ArgStyles.Enchant3    'spellName, itemId, itemName
                                EventData.spellName = EventString(pt) : pt += 1
                                EventData.itemId = EventString(pt) : pt += 1

                            Case ArgStyles.EnvType1
                                EventData.envType = EventString(pt) : pt += 1

                            Case ArgStyles.Aura1       'auraType
                                EventData.auraType = EventString(pt) : pt += 1

                            Case ArgStyles.AuraDoze2   'auraType, amount
                                EventData.auraType = EventString(pt) : pt += 1
                                EventData.amount = EventString(pt) : pt += 1

                            Case ArgStyles.Miss1       'missType
                                EventData.missType = EventString(pt) : pt += 1

                            Case ArgStyles.Miss2       'missType, Absorption ... WOTLK 3.2
                                EventData.missType = EventString(pt) : pt += 1
                                EventData.damageAbsorb = EventString(pt) : pt += 1

                            Case ArgStyles.Damage8     'amount, school, resisted, blocked, absorbed, critical, glancing, crushing
                                EventData.amount = EventString(pt) : pt += 1
                                EventData.damageDone = EventData.amount
                                EventData.spellSchool = EventString(pt) : pt += 1
                                EventData.resisted = EventString(pt) : pt += 1
                                EventData.blocked = EventString(pt) : pt += 1
                                EventData.absorbed = EventString(pt) : pt += 1
                                EventData.critical = getCritical(EventString(pt)) : pt += 1
                                EventData.glancing = getCritical(EventString(pt)) : pt += 1
                                EventData.crushing = getCritical(EventString(pt)) : pt += 1

                            Case ArgStyles.Damage9     'amount, school, resisted, blocked, absorbed, critical, glancing, crushing
                                EventData.amount = EventString(pt) : pt += 1
                                EventData.damageDone = EventData.amount
                                EventData.damageAbsorb = EventString(pt) : pt += 1
                                EventData.spellSchool = EventString(pt) : pt += 1
                                EventData.resisted = EventString(pt) : pt += 1
                                EventData.blocked = EventString(pt) : pt += 1
                                EventData.absorbed = EventString(pt) : pt += 1
                                EventData.critical = getCritical(EventString(pt)) : pt += 1
                                EventData.glancing = getCritical(EventString(pt)) : pt += 1
                                EventData.crushing = getCritical(EventString(pt)) : pt += 1

                            Case ArgStyles.Heal2       'amount + crit ... TBC
                                EventData.amount = EventString(pt) : pt += 1
                                EventData.heal = EventData.amount
                                EventData.critical = getCritical(EventString(pt)) : pt += 1

                            Case ArgStyles.Heal3       'amount + overheal + crit ... WOTLK 3.0
                                EventData.amount = EventString(pt) : pt += 1
                                EventData.heal = EventData.amount
                                EventData.overHeal = EventString(pt) : pt += 1
                                EventData.critical = getCritical(EventString(pt)) : pt += 1

                            Case ArgStyles.Heal4       'amount + overheal + absorption + crit ... WOTLK 3.2
                                EventData.amount = EventString(pt) : pt += 1
                                EventData.heal = EventData.amount
                                EventData.overHeal = EventString(pt) : pt += 1
                                EventData.healAbsorb = EventString(pt) : pt += 1
                                EventData.critical = getCritical(EventString(pt)) : pt += 1

                            Case ArgStyles.Energize2   'amount, powerType
                                EventData.amount = EventString(pt) : pt += 1
                                EventData.powerType = EventString(pt) : pt += 1

                            Case ArgStyles.Drain3      'amount, powerType, extraAmount
                                EventData.amount = EventString(pt) : pt += 1
                                EventData.powerType = EventString(pt) : pt += 1
                                EventData.extraAmount = EventString(pt) : pt += 1

                            Case Else
                        End Select
                    Next
                End If
            End If
        Next
    End Sub

    Public Function ClangHexToInt(ByVal Str As String) As UInteger
        Dim f As UInteger

        f = CUInt(Replace(Str, "0x", "&h"))

        Return f
    End Function

    Private Function getCritical(ByVal Value As String) As Integer
        If Value = "nil" Then
            Return 0
        ElseIf Value = "1" Then
            Return 1
        Else
            Dim EH As New ErrorHandler
            EH.ErrorLog("EventDB", "getCritical", "The value must be nil or 1", "Value=" & Value)

            Return 0
        End If
    End Function

    Public Function GetUnitId(ByVal UnitIdString As String) As Long
        Dim uid As String = UnitIdString

        If uid.Length >= 4 And CInt(Replace(Mid(uid, 1, 4), "0x", "&h")) < 128 Then
            uid = Mid(uid, 5)
        ElseIf uid.Length >= 3 And LCase(Mid(uid, 1, 2)) = "0x" Then
            uid = Mid(uid, 3)
        End If

        Try
            uid = "&h" & uid
            Return CLng(uid)
        Catch ex As Exception
            Return 0
        End Try
    End Function

    Public Function GetEventIdx(ByRef EventName As String) As EventDB.EventIndex
        Dim i As Integer

        For i = 0 To EventName.Length - 1
            If EventName(i) = EventName Then
                Return i
            End If
        Next

        Dim EH As New ErrorHandler
        EH.ErrorLog("EventDB", "GetEventIdx", "Found New Event", "Value=" & EventName)

        Return EventIndex.UNKNOWN
    End Function
#End Region
End Class
